﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Design;
using System.Drawing.Drawing2D;
using System.Drawing.Text;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace WeForms.Controls
{
    public class RadioButtonGroupItemCollection : ObservableCollection<object>
    {
    }

    [DefaultEvent("SelectedIndexChanged")]
    public class RadioButtonGroup : Control, ISupportInitialize
    {
        private int _initCount;
        private Size _prefferedSize;
        private readonly List<Rectangle> _bounds;
        private bool _needsUpdating = true;

        private TouchButtonColor _unselectedColorScheme;
        /// <summary>
        /// Gets or sets the color scheme used for unselected elements 
        /// </summary>
        /// <value>The color scheme.</value>
        public TouchButtonColor UnselectedColorScheme
        {
            get { return _unselectedColorScheme; }
            set
            {
                if (_unselectedColorScheme == value) return;
                _unselectedColorScheme = value;
                Invalidate();
            }
        }

        private TouchButtonColor _selectedColorScheme;
        /// <summary>
        /// Gets or sets the color scheme used for selected elements 
        /// </summary>
        /// <value>The color scheme.</value>
        public TouchButtonColor SelectedColorScheme
        {
            get { return _selectedColorScheme; }
            set
            {
                if (_selectedColorScheme == value) return;
                _selectedColorScheme = value;
                Invalidate();
            }
        }

        private int _selectedIndex;
        /// <summary>
        /// Gets or sets the index of the selected item
        /// </summary>
        /// <value>The index of the selected item.</value>
        public int SelectedIndex
        {
            get { return _selectedIndex; }
            set
            {
                if (_selectedIndex == value) return;
                _selectedIndex = Math.Min(Math.Max(0, value), Items.Count - 1);
                Invalidate();
                OnSelectedIndexChanged(EventArgs.Empty);
            }
        }

        /// <summary>
        /// Gets a list of items, displayed within this group.
        /// </summary>
        /// <value>The items.</value>
        [Editor("System.Windows.Forms.Design.StringCollectionEditor, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof(UITypeEditor))]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        public RadioButtonGroupItemCollection Items { get; private set; }

        /// <summary>
        /// Initializes a new instance of the <see cref="RadioButtonGroup"/> class.
        /// </summary>
        public RadioButtonGroup()
        {
            SetStyle(ControlStyles.UserPaint, true);
            SetStyle(ControlStyles.AllPaintingInWmPaint, true);
            SetStyle(ControlStyles.ContainerControl, true);
            SetStyle(ControlStyles.OptimizedDoubleBuffer, true);

            _bounds = new List<Rectangle>();
            Font = TouchButtonRenderer.DefaultFont;
            Items = new RadioButtonGroupItemCollection();
            Items.CollectionChanged += OnItemsCollectionChanged;
            Padding = new Padding(20);
        }

        /// <summary>
        /// Called when items collection changed.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="CollectionChangedEventArgs"/> instance containing the event data.</param>
        private void OnItemsCollectionChanged(object sender, CollectionChangedEventArgs e)
        {
            _needsUpdating = true;
            if (IsIniting) return;
            PerformLayout();
            Invalidate();
        }

        /// <summary>
        /// Retrieves the size of a rectangular area into which a control can be fitted.
        /// </summary>
        /// <param name="proposedSize">The custom-sized area for a control.</param>
        /// <returns>
        /// An ordered pair of type <see cref="T:System.Drawing.Size"/> representing the width and height of a rectangle.
        /// </returns>
        public override Size GetPreferredSize(Size proposedSize)
        {
            if (_needsUpdating)
            {
                if (IsIniting) return proposedSize;
                _bounds.Clear();
                Size fullSize = new Size();

                for (int i = 0; i < Items.Count; i++)
                {
                    object item = Items[i];
                    Size s = GetItemSize(item);

                    _bounds.Add(new Rectangle(fullSize.Width, 0, s.Width, s.Height));
                    fullSize.Width += s.Width;
                    fullSize.Height = Math.Max(fullSize.Height, s.Height);
                }

                _prefferedSize = new Size(Math.Max(fullSize.Width, Padding.Horizontal),
                                          Math.Max(fullSize.Height, Padding.Vertical));
                _needsUpdating = false;
            }
            return _prefferedSize;
        }

        protected override void OnPaddingChanged(EventArgs e)
        {
            base.OnPaddingChanged(e);
            _needsUpdating = true;
            PerformLayout();
            Invalidate();
        }

        /// <summary>
        /// Gets the size of the item.
        /// </summary>
        /// <param name="item">The item.</param>
        /// <returns></returns>
        protected internal Size GetItemSize(object item)
        {
            Size s = TextRenderer.MeasureText(GetItemText(item), Font);
            s.Width += Padding.Horizontal;
            s.Height += Padding.Vertical;
            return s;
        }

        /// <summary>
        /// Gets the item text.
        /// </summary>
        /// <param name="item">The item.</param>
        /// <returns></returns>
        protected internal string GetItemText(object item)
        {
            return string.Format("{0}", item);
        }

        /// <summary>
        /// Performs the work of setting the specified bounds of this control.
        /// </summary>
        /// <param name="x">The new <see cref="P:System.Windows.Forms.Control.Left"/> property value of the control.</param>
        /// <param name="y">The new <see cref="P:System.Windows.Forms.Control.Top"/> property value of the control.</param>
        /// <param name="width">The new <see cref="P:System.Windows.Forms.Control.Width"/> property value of the control.</param>
        /// <param name="height">The new <see cref="P:System.Windows.Forms.Control.Height"/> property value of the control.</param>
        /// <param name="specified">A bitwise combination of the <see cref="T:System.Windows.Forms.BoundsSpecified"/> values.</param>
        protected override void SetBoundsCore(int x, int y, int width, int height, BoundsSpecified specified)
        {
            if(AutoSize)
            {
                width = PreferredSize.Width;
                height = PreferredSize.Width;
            }
            base.SetBoundsCore(x, y, width, height, specified);
        }

        /// <summary>
        /// Raises the <see cref="E:System.Windows.Forms.Control.Paint"/> event.
        /// </summary>
        /// <param name="e">A <see cref="T:System.Windows.Forms.PaintEventArgs"/> that contains the event data.</param>
        protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);
            if (IsIniting) return;

            if (_needsUpdating)
            {
                PerformLayout();
                GetPreferredSize(_prefferedSize);
            }

            e.Graphics.InterpolationMode = InterpolationMode.NearestNeighbor;
            e.Graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
            e.Graphics.TextRenderingHint = TextRenderingHint.ClearTypeGridFit;

            for (int i = 0; i < Items.Count; i++)
            {
                object item = Items[i];
                Rectangle bounds = _bounds[i];
                RadioButtonGroupRenderer.PaintItem(this, item, e.Graphics, bounds, i);
            }
        }


        /// <summary>
        /// Raises the <see cref="E:System.Windows.Forms.Control.MouseDown"/> event.
        /// </summary>
        /// <param name="e">A <see cref="T:System.Windows.Forms.MouseEventArgs"/> that contains the event data.</param>
        protected override void OnMouseDown(MouseEventArgs e)
        {
            base.OnMouseDown(e);
            if (IsIniting) return;
            for (int i = 0; i < Items.Count; i++)
            {
                if (_bounds[i].Contains(e.Location))
                {
                    SelectedIndex = i;
                    return;
                }
            }
        }

        /// <summary>
        /// Occurs when the selected index changed.
        /// </summary>
        public event EventHandler SelectedIndexChanged;
        /// <summary>
        /// Raises the <see cref="SelectedIndexChanged"/> event.
        /// </summary>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        protected virtual void OnSelectedIndexChanged(EventArgs e)
        {
            if (SelectedIndexChanged != null)
                SelectedIndexChanged(this, e);
        }

        #region Implementation of ISupportInitialize

        private bool IsIniting
        {
            get { return _initCount > 0; }
        }

        public void BeginInit()
        {
            _initCount++;
        }

        public void EndInit()
        {
            _initCount--;
            if(_initCount <= 0)
            {
                _initCount = 0;
                _needsUpdating = true;
                PerformLayout();
                Invalidate();
            }
           
        }

        #endregion
    }
}
